package com.meta.platform.sms.service.invoke.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.http.MethodType;
import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.profile.IClientProfile;
import com.github.pagehelper.util.StringUtil;
import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.common.collect.Sets;
import com.meta.framework.common.utils.spring.SpringUtils;
import com.meta.framework.util.BeanUtil;
import com.meta.platform.sms.config.AliSmsPushConfig;
import com.meta.platform.sms.config.HuaweiSmsPushConfig;
import com.meta.platform.sms.constants.SmsChannel;
import com.meta.platform.sms.domain.SmsConfig;
import com.meta.platform.sms.request.InvokeThirdSmsParam;
import com.meta.platform.sms.request.SendSmsParam;
import com.meta.platform.sms.service.invoke.InvokeThirdSmsService;
import com.meta.platform.sms.utils.PhoneUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.methods.RequestBuilder;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 *  华为云短信发送
 *  部分源码参考华为云官方文档
 * @Title:      HuaweiSmsInvokeServiceImpl
 * @Package:    com.meta.platform.sms.service.invoke.impl.HuaweiSmsInvokeServiceImpl
 * @Author:     M.simple
 * @Remark:     The modified content
 * @CreateDate: 2021/2/1 3:30 下午
 * @Version:    v2.0
 */
@Service("huaweiSms")
@Slf4j
public class HuaweiSmsInvokeServiceImpl implements InvokeThirdSmsService {

	/**
	 * CloseableHttpClient
	 */
	public volatile static CloseableHttpClient closeableHttpClient;

	/**
	 * 无需修改,用于格式化鉴权头域,给"X-WSSE"参数赋值
	 */
	private static final String WSSE_HEADER_FORMAT = "UsernameToken Username=\"%s\",PasswordDigest=\"%s\",Nonce=\"%s\",Created=\"%s\"";
	/**
	 * 无需修改,用于格式化鉴权头域,给"Authorization"参数赋值
	 */
	private static final String AUTH_HEADER_VALUE = "WSSE realm=\"SDP\",profile=\"UsernameToken\",type=\"Appkey\"";

	private static final String SEND_MSG = "sms/batchSendSms/v1";

	private HuaweiSmsInvokeServiceImpl hwYunMsgMaker;

	@Autowired(required = false)
	public HuaweiSmsPushConfig huaweiSmsPushConfig;

	public HuaweiSmsInvokeServiceImpl() {
		closeableHttpClient = getHttpClient();
	}

	/**
	 * 获取CloseableHttpClient
	 *
	 * @return CloseableHttpClient
	 */
	private static CloseableHttpClient getHttpClient() {
		if (closeableHttpClient == null) {
			synchronized (HuaweiSmsInvokeServiceImpl.class) {
				if (closeableHttpClient == null) {
					try {
						// 为防止因HTTPS证书认证失败造成API调用失败,需要先忽略证书信任问题
						closeableHttpClient = HttpClients.custom()
								.setSSLContext(new SSLContextBuilder().loadTrustMaterial(null,
										(x509CertChain, authType) -> true).build())
								.setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE)
								.build();
					} catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) {
						e.printStackTrace();
					}
				}
			}
		}
		return closeableHttpClient;
	}

	/**
	 * 构造请求Body体
	 *
	 * @param sender
	 * @param receiver
	 * @param templateId
	 * @param templateParas
	 * @param statusCallbackUrl
	 * @param signature         | 签名名称,使用国内短信通用模板时填写
	 * @return
	 */
	static String buildRequestBody(String sender, String receiver, String templateId, String templateParas,
								   String statusCallbackUrl, String signature) {
		if (null == sender || null == receiver || null == templateId || sender.isEmpty() || receiver.isEmpty()
				|| templateId.isEmpty()) {
			System.out.println("buildRequestBody(): sender, receiver or templateId is null.");
			return null;
		}
		List<NameValuePair> keyValues = new ArrayList<>();

		keyValues.add(new BasicNameValuePair("from", sender));
		keyValues.add(new BasicNameValuePair("to", receiver));
		keyValues.add(new BasicNameValuePair("templateId", templateId));
		if (null != templateParas && !templateParas.isEmpty()) {
			keyValues.add(new BasicNameValuePair("templateParas", templateParas));
		}
		if (null != statusCallbackUrl && !statusCallbackUrl.isEmpty()) {
			keyValues.add(new BasicNameValuePair("statusCallback", statusCallbackUrl));
		}
		if (null != signature && !signature.isEmpty()) {
			keyValues.add(new BasicNameValuePair("signature", signature));
		}

		return URLEncodedUtils.format(keyValues, Charset.forName("UTF-8"));
	}

	/**
	 * 构造X-WSSE参数值
	 *
	 * @param appKey
	 * @param appSecret
	 * @return
	 */
	static String buildWsseHeader(String appKey, String appSecret) {
		if (null == appKey || null == appSecret || appKey.isEmpty() || appSecret.isEmpty()) {
			System.out.println("buildWsseHeader(): appKey or appSecret is null.");
			return null;
		}
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
		String time = sdf.format(new Date()); //Created
		String nonce = UUID.randomUUID().toString().replace("-", ""); //Nonce

		byte[] passwordDigest = DigestUtils.sha256(nonce + time + appSecret);
		String hexDigest = Hex.encodeHexString(passwordDigest);

		//如果JDK版本是1.8,请加载原生Base64类,并使用如下代码
		String passwordDigestBase64Str = Base64.getEncoder().encodeToString(hexDigest.getBytes()); //PasswordDigest
		//如果JDK版本低于1.8,请加载三方库提供Base64类,并使用如下代码
		//String passwordDigestBase64Str = Base64.encodeBase64String(hexDigest.getBytes(Charset.forName("utf-8"))); //PasswordDigest
		//若passwordDigestBase64Str中包含换行符,请执行如下代码进行修正
		//passwordDigestBase64Str = passwordDigestBase64Str.replaceAll("[\\s*\t\n\r]", "");

		return String.format(WSSE_HEADER_FORMAT, appKey, passwordDigestBase64Str, nonce, time);
	}

	@Override
	public String outerSmsChannelName() {
		return null;
	}

	@Override
	public InvokeThirdSmsParam generateInvokeParam(SmsConfig smsConfig, SendSmsParam originalParam) {
		InvokeThirdSmsParam result = new InvokeThirdSmsParam();
		BeanUtil.copyProperties(smsConfig, result);
		result.setMobiles(originalParam.getMobiles());
		result.setNativeCode(originalParam.getNativeCode());
		result.setBizId(UUID.randomUUID().toString());
		String smsParams = originalParam.getSmsParams();
		result.setOriginalSmsParam(smsParams);
		String outerSmsTemplate = smsConfig.getOuterSmsTemplate();
		if (Strings.isNullOrEmpty(smsParams)) {
			result.setFinalSmsContent(smsParams);
			return result;
		}
		JSONObject object = JSON.parseObject(smsParams);
		Set<String> jsonKeySets = object.keySet();
		Map<String, Object> params = new HashMap<>();
		for (String key : jsonKeySets) {
			params.put(key, object.get(key));
		}
		List<String> templateParams = new ArrayList<String>();
		boolean flag = true;
		int count = 0;
		while (flag && count < 5) { //最多5个变量替换
			count++;
			int startIndex = outerSmsTemplate.indexOf("${");
			if (startIndex > -1) {
				int endIndex = outerSmsTemplate.indexOf("}");
				String currentParamName = outerSmsTemplate.substring(startIndex + 2, endIndex);
				String replaceContext = "${" + currentParamName + "}";
				outerSmsTemplate = outerSmsTemplate.replace(replaceContext, params.get(currentParamName).toString());

				templateParams.add(params.get(currentParamName).toString());
			} else {
				flag = false;
			}
		}
		result.setFinalSmsContent(outerSmsTemplate);
		result.setTemplateParas(templateParams.toArray(new String[templateParams.size()]));
		return result;
	}

	@Override
	public Boolean invokeThirdSmsService(InvokeThirdSmsParam invokeThirdSmsParam) {

		try {
			//APP接入地址+接口访问URI
			String url = String.format("https://%s.%s.%s/%s", huaweiSmsPushConfig.getProduct(), huaweiSmsPushConfig.getEndPoint(),
					huaweiSmsPushConfig.getDomain(), SEND_MSG);
			//String url = "https://rtcsms.cn-north-1.myhuaweicloud.com:10743/sms/batchSendSms/v1"; //APP接入地址+接口访问URI
			//APP_Key
			String appKey = huaweiSmsPushConfig.getAccessKey();
			//APP_Secret
			String appSecret = huaweiSmsPushConfig.getSecret();
			//国内短信签名通道号或国际/港澳台短信通道号
			String sender = "8821022441788";
			String signature = invokeThirdSmsParam.getSignName();
			//模板ID
			String templateId = invokeThirdSmsParam.getOuterSmsTemplateCode();
			//模板变量
			String templateParas = JSON.toJSONString(invokeThirdSmsParam.getTemplateParas());
			String receiver = invokeThirdSmsParam.getMobiles().get(0);
			//请求Body,不携带签名名称时,signature请填null
			String body = buildRequestBody(sender, receiver, templateId, templateParas, "", signature);
			if (null == body || body.isEmpty()) {
				return false;
			}

			//请求Headers中的X-WSSE参数值
			String wsseHeader = buildWsseHeader(appKey, appSecret);
			if (null == wsseHeader || wsseHeader.isEmpty()) {
				return false;
			}
			HttpResponse response = closeableHttpClient.execute(RequestBuilder.create("POST")
					.setUri(url)
					.addHeader(HttpHeaders.CONTENT_TYPE, "application/x-www-form-urlencoded")
					.addHeader(HttpHeaders.AUTHORIZATION, AUTH_HEADER_VALUE)
					.addHeader("X-WSSE", wsseHeader)
					.setEntity(new StringEntity(body)).build());
			log.info("-------------send huawei sms, res:{}", EntityUtils.toString(response.getEntity()));
			return true;
		} catch (Exception e) {
			log.error(ExceptionUtils.getStackTrace(e));
		}

		return null;
	}
}
